package com.gitee.wsl.ext.collection


import kotlin.math.abs

/**
 * @return the next element if it exists, `null` otherwise
 */
fun <T> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null

fun <T> Iterable<T>.findDuplicates(
    comparison: (T, T) -> Boolean
): List<T> {
    return filter { item ->
        this.count { comparison(it, item)  } > 1
    }
}

inline fun <T, R : Comparable<R>> Iterable<T>.maxValueBy(selector: (T) -> R): R? {
    val iterator = iterator()
    if (!iterator.hasNext()) return null
    val maxElem = iterator.next()
    var maxValue = selector(maxElem)
    if (!iterator.hasNext()) return maxValue
    do {
        val e = iterator.next()
        val v = selector(e)
        if (maxValue < v) {
            maxValue = v
        }
    } while (iterator.hasNext())
    return maxValue
}

fun <R : Comparable<R>> Iterable<R>.maxValueIndex(): Int {
    val iterator = iterator()
    if (!iterator.hasNext()) return -1
    var index = 0
    val maxValue = this.max()
    do {
        val e = iterator.next()
        if (maxValue == e) {
            break
        }
        index++
    } while (iterator.hasNext())
    return index
}


fun <T> Iterator<T>.skipUntil(predicate: (T) -> Boolean): T? {
    while (hasNext()) {
        val v = next()
        if (predicate(v)) return v
    }

    return null
}


fun <T> Iterable<T>.getByIndex(index: Int): T {
    return when (this) {
        is List ->
            if (size >= index - 1) this[index - 1]
            else throw NoSuchElementException("Collection size is lower than $index.")
        else -> {
            val iterator = iterator()
            if (!iterator.hasNext())
                throw NoSuchElementException("Collection size is lower than $index.")
            var value: T = iterator.next()

            for (i in 0 until index - 1) {
                if (!iterator.hasNext())
                    throw NoSuchElementException("Collection size is lower than $index.")
                else value = iterator.next()
            }
            value
        }
    }
}


fun <T> Iterable<T>.getByIndexOrNull(index: Int): T? {
    return when (this) {
        is List -> getOrNull(index - 1)
        else -> {
            val iterator = iterator()
            if (!iterator.hasNext()) return null
            var value: T = iterator.next()

            for (i in 0 until index - 1) {
                if (!iterator.hasNext()) return null else value = iterator.next()
            }
            value
        }
    }
}


fun <T:Number> Collection<T>.findClosestPositiveValue(value: T): T? {
    if (isEmpty()) return null
    var closestValue: T? = null
    forEach { checkedValue ->
        closestValue = when {
            closestValue == null -> checkedValue
            abs(closestValue.toDouble() - value.toDouble()) > abs(checkedValue.toDouble() - value.toDouble()) -> checkedValue
            else -> closestValue
        }
    }
    return closestValue
}

fun <T:Comparable<T>,Q:Comparable<Q>> Iterable<T>.findClosest(input: T,minBlock:(T,T)-> Q) = filter { it <= input }.minByOrNull { minBlock(input,it) }

/**
 * Like [indexOfFirst] but returns null instead of -1. If the not found.
 */
inline fun <T> Iterable<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? =
    indexOfFirst(predicate).takeIf { it != -1 }



inline fun <T> Iterable<T>.findNthOrNull(n: Int, predicate: (T) -> Boolean): T? {
    var count = 0
    for (element in this) {
        if (predicate(element)) {
            if (count == n) return element
            count++
        }
    }

    return null
}

inline fun <T> Iterable<T>.findNth(n: Int, predicate: (T) -> Boolean): T {
    return findNthOrNull(n, predicate)
        ?: throw NoSuchElementException("No ${n + 1} element(s) matching predicate found.")
}

inline fun <reified R> Iterable<*>.findNthOfTypeOrNull(n: Int): R? {
    var count = 0
    for (element in this) {
        if (element is R) {
            if (count == n) return element
            count++
        }
    }

    return null
}

inline fun <reified R> Iterable<*>.findNthOfType(n: Int): R {
    return findNthOfTypeOrNull<R>(n)
        ?: throw NoSuchElementException("No ${n + 1} element(s) of type ${R::class} found.")
}

inline fun <T, reified R> Iterable<T>.findMapped(transform: (T) -> R?): R? {
    for (item in this) {
        return transform(item) ?: continue
    }
    return null
}

